home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2007 December
/
PCWKCD1207B.iso
/
Blogowanie poza sfera
/
Flock 1.0 beta
/
flock-1.0RC3.en-US.win32.exe
/
flock
/
components
/
flockFeedManager.js
< prev
next >
Wrap
Text File
|
2007-10-18
|
69KB
|
2,238 lines
// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2007
// http://flock.com
//
// This file may be used under the terms of of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL
Components.utils.import("resource:///modules/FlockCryptoHash.jsm");
const FEED_CONTRACTID = '@flock.com/feed;1';
const FEED_CLASSID = Components.ID('2f7499f4-b47f-4350-a6c5-aee66520c720');
const FEED_CLASSNAME = 'Flock Feed';
const ITEM_CONTRACTID = '@flock.com/feed-item;1';
const ITEM_CLASSID = Components.ID('f97d9a24-2463-4e8d-b383-9e41f2bc60e8');
const ITEM_CLASSNAME = 'Flock Feed Item';
const FC_CONTRACTID = '@flock.com/feed-context;1';
const FC_CLASSID = Components.ID('6e95c222-6707-42cf-aa1b-12baea3983e6');
const FC_CLASSNAME = 'Flock Feed Context';
const FF_CONTRACTID = '@flock.com/feed-folder;1';
const FF_CLASSID = Components.ID('16b7ab70-c745-4f53-83a0-3375a031a113');
const FF_CLASSNAME = 'Flock Feed Folder';
const FM_CONTRACTID = '@flock.com/feed-manager;1';
const FM_CLASSID = Components.ID('287848be-bbc9-42aa-953e-2ec916eb8d49');
const FM_CLASSNAME = 'Flock Feed Manager';
const FEED_CONTENT_FILE = 'feedcontent.sqlite';
const EXCERPT_MAX_WORDS = 50;
const TITLE_TRIM_MAX_CHARS = 30;
const PURGE_RUN_INTERVAL = 500;
const PURGE_SLEEP_INTERVAL = 1500;
const UNKNOWN_FEED_TYPE = 'unknown';
const URI_FEED_PROPERTIES = 'chrome://flock/locale/feeds/feeds.properties';
const URN_FEED_ROOT = 'urn:flock:feedroot';
const NEWS_CONTEXT_NAME = 'news';
const LIVEMARKS_CONTEXT_NAME = 'livemarks';
const ATTR_URI_LIST = ['action', 'href', 'src', 'longdesc', 'usemap',
'cite'];
/* prefs */
const PREF_EXPIRATION_TIME_BRANCH = 'flock.feeds.expiration_time';
const PREF_EXPIRATION_TIME = '.subscriptions';
const PREF_METADATA_EXPIRATION_TIME = '.metadata_only';
const DEFAULT_EXPIRATION_TIME = 60;
const DEFAULT_METADATA_EXPIRATION_TIME = 24 * 60;
const FAVICON_EXPIRATION_TIME = 24 * 60 * 60 * 1000;
/* cardinal to danphe migration */
const FLOCK_NS = 'http://flock.com/rdf#';
const OLD_FEEDS_RDF_FILE = 'flock_subscriptions.rdf';
const OLD_FEEDS_RDF_FILE_RELIC = 'flock_subscriptions_old.rdf';
const SUBSCRIPTIONS_RDF_ROOT = 'urn:flock:feed:subscriptions';
const OLD_FLAGGED_RDF_FILE = 'flock_feeds_flagged.rdf';
const OLD_FLAGGED_RDF_FILE_RELIC = 'flock_feeds_flagged_old.rdf';
const FLAGGED_RDF_ROOT = 'urn:flock:feed';
const OLD_ROOT_RDF_FILE = 'flock_feeds_root.rdf';
const OLD_DISCOVERY_RDF_FILE = 'flock_feeds_discovery.rdf';
const OLD_FEED_DATA_DIR = 'feeds';
const OLD_FEED_DATA_FILENAME = 'feed.rdf';
const OLD_FEED_DATA_RDF_PREFIX = 'urn:flock:feed:';
const OLD_FEED_DATA_POST_PREFIX = OLD_FEED_DATA_RDF_PREFIX + 'post:';
const FORMAT_CONVERSIONS = { 'Atom 1.0': 'atom',
'Atom 0.3': 'atom03',
'RSS 2.0' : 'rss2',
'RSS 1.0' : 'rss1',
'RSS 0.9' : 'rss090',
'RSS 0.91': 'rss091',
'RSS 0.92': 'rss092',
'RSS 0.93': 'rss093',
'RSS 0.94': 'rss094',
};
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
/* from nspr's prio.h */
const PR_RDONLY = 0x01;
const PR_WRONLY = 0x02;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
const PR_SYNC = 0x40;
const PR_EXCL = 0x80;
var gIOService = null;
var gFeedStorage = null;
var gReadMoreBlurb = null;
function getObserverService() {
return Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService);
}
function excerptText(text, link) {
var words = text.split(/\s+/);
if (words.length <= EXCERPT_MAX_WORDS)
return text;
var excerpt = words.slice(0, EXCERPT_MAX_WORDS + 1).join(' ');
if (link)
excerpt += ' <a href="' + link + '">' + gReadMoreBlurb + '</a>';
return excerpt;
}
function checkEmpty(value) {
if (value == null) return null;
var str = value.toString();
return str ? str : null;
}
function makeURI(uri) {
try {
return gIOService.newURI(uri, null, null);
}
catch (e) {
return null;
}
}
function getFeedNode(url, coop) {
var urn = coop.Feed.get_id({ URL: url });
return coop.get(urn);
}
function insertFavicon(feedNode) {
var age = Date.now() - feedNode.lastFaviconFetch.getTime();
if (age < FAVICON_EXPIRATION_TIME)
return;
var url = makeURI(feedNode.link);
if (!url)
return;
if (!/^https?/.test(url.scheme))
return;
feedNode.lastFaviconFetch = new Date();
var faviconURL = url.prePath + '/favicon.ico';
var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
.createInstance(Ci.nsIXMLHttpRequest);
hr.onload = function(evt) {
if (evt.target.status == 200)
feedNode.favicon = faviconURL;
};
hr.backgroundRequest = true;
hr.open('HEAD', faviconURL);
hr.send(null);
}
function getContextsForFeed(feedNode, coop) {
var contexts = [];
var parents = feedNode.getParents();
for each (var parent in parents) {
var node = parent;
while (!node.isInstanceOf(coop.FeedContext)) {
var nodeParents = node.getParents();
if (nodeParents.length)
node = nodeParents[0];
else
break;
}
if (node.isInstanceOf(coop.FeedContext))
contexts.push(node);
}
return contexts;
}
function contextHasObject(contextName, obj, coop) {
if (obj.isInstanceOf(coop.FeedContext))
return obj.name == contextName;
var parents = obj.getParents();
for each (var parent in parents) {
var node = parent;
while (!node.isInstanceOf(coop.FeedContext)) {
var nodeParents = node.getParents();
if (nodeParents.length)
node = nodeParents[0];
else
break;
}
if (node.isInstanceOf(coop.FeedContext) && node.name == contextName)
return true;
}
return false;
}
function setFeedIndexable(feedNode, indexable) {
feedNode.isIndexable = indexable;
var items = feedNode.children.enumerate();
while (items.hasMoreElements()) {
var item = items.getNext();
item.isIndexable = indexable;
}
}
function updateNextRefresh(feedNode, coop) {
var contexts = getContextsForFeed(feedNode, coop);
if (contexts.length == 0)
return;
var refreshInterval = contexts[0].refreshInterval;
if (contexts.length > 1) {
for (var i = 1; i < contexts.length; i++) {
refreshInterval = Math.min(refreshInterval, contexts[i].refreshInterval);
}
}
feedNode.nextRefresh = new Date(Date.now() + refreshInterval * 1000);
}
function feedItemSorter(a, b) {
var res = a.datevalue - b.datevalue;
if (res == 0)
return b.indexvalue - a.indexvalue;
else
return res;
}
function createStatement(dbconn, sql) {
var stmt = dbconn.createStatement(sql);
var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
.createInstance(Ci.mozIStorageStatementWrapper);
wrapper.initialize(stmt);
return wrapper;
}
function FeedStorage() {
var dbfile = Cc['@mozilla.org/file/directory_service;1']
.getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
dbfile.append(FEED_CONTENT_FILE);
var storageService = Cc['@mozilla.org/storage/service;1']
.getService(Ci.mozIStorageService);
this._DBConn = storageService.openDatabase(dbfile);
var schema = 'id STRING PRIMARY KEY, feed STRING, ' +
'content STRING, excerpt STRING';
try {
this._DBConn.createTable('feed_content', schema);
}
catch (e) { }
this._queryContent = createStatement(this._DBConn,
'SELECT content FROM feed_content WHERE id = :id');
this._queryExcerpt = createStatement(this._DBConn,
'SELECT excerpt FROM feed_content WHERE id = :id');
this._removeItem = createStatement(this._DBConn,
'DELETE FROM feed_content where id = :id');
this._insertItem = createStatement(this._DBConn,
'INSERT INTO feed_content (id, feed, content, excerpt) ' +
'VALUES (:id, :feed, :content, :excerpt)');
this._removeFeed = createStatement(this._DBConn,
'DELETE FROM feed_content where feed = :feed');
}
FeedStorage.prototype = {
deleteItem: function FS_deleteItem(id) {
this._removeItem.params.id = id;
this._removeItem.step();
this._removeItem.reset();
},
saveItem: function FS_saveItem(id, feed, content, excerpt) {
this.deleteItem(id);
var pp = this._insertItem.params;
pp.id = id;
pp.feed = feed;
pp.content = content;
pp.excerpt = excerpt;
this._insertItem.step();
this._insertItem.reset();
},
deleteFeed: function FS_deleteFeed(feed) {
this._removeFeed.params.feed = feed;
this._removeFeed.step();
this._removeFeed.reset();
},
getContent: function FS_getContent(id) {
return this._getData(id, this._queryContent, 'content');
},
getExcerpt: function FS_getExcerpt(id) {
return this._getData(id, this._queryExcerpt, 'excerpt');
},
beginTransaction: function FS_beginTransation() {
this._DBConn.beginTransaction();
},
commitTransaction: function FS_commitTransation() {
this._DBConn.commitTransaction();
},
rollbackTransaction: function FS_rollbackTransation() {
this._DBConn.rollbackTransaction();
},
_getData: function FS__getData(id, stmt, field) {
stmt.reset();
stmt.params.id = id;
var data = null;
if (stmt.step())
data = stmt.row[field];
stmt.reset();
return data;
}
}
function Feed(feedNode, context, coop) {
this._feedNode = feedNode;
this._context = context;
this._coop = coop;
}
Feed.prototype = {
getURL: function FEED_getURL() {
return makeURI(this._feedNode.URL);
},
getFinalURL: function FEED_getFinalURL() {
return makeURI(this._feedNode.finalURL);
},
getLink: function FEED_getLink() {
return makeURI(this._feedNode.link);
},
getType: function FEED_getType() {
return this._feedNode.format;
},
getTitle: function FEED_getTitle() {
return checkEmpty(this._feedNode.name);
},
getSubtitle: function FEED_getSubtitle() {
return checkEmpty(this._feedNode.subtitle);
},
getImage: function FEED_getImage() {
return makeURI(this._feedNode.image);
},
getFavicon: function FEED_getFavicon() {
return makeURI(this._feedNode.favicon);
},
getAuthor: function FEED_getAuthor() {
return checkEmpty(this._feedNode.author);
},
getPubDate: function FEED_getPubDate() {
return this._feedNode.datevalue;
},
getItemCount: function FEED_getItemCount() {
return this._feedNode.count;
},
getItem: function FEED_getItem(index) {
var items = this.getItems();
var i = 0;
while (items.hasMoreElements()) {
var item = items.getNext();
if (i == index)
return item;
i++;
}
return null;
},
getItems: function FEED_getItems() {
var filter = function(feed, item, coop) {
return new FeedItem(feed, item, coop);
};
return this._enumerateItems(filter);
},
refresh: function FEED_refresh() {
this._feedNode.nextRefresh = new Date();
},
getContext: function FEED_getContext() {
return this._context;
},
getUnreadCount: function FEED_getUnreadCount() {
return this._feedNode.unseenItems;
},
getUnreadItems: function FEED_getUnreadItems() {
var filter = function(feed, item, coop) {
if (item.unseen)
return new FeedItem(feed, item, coop);
};
return this._enumerateItems(filter);
},
getFlaggedItems: function FEED_getFlaggedItems() {
var filter = function(feed, item, coop) {
if (item.flagged)
return new FeedItem(feed, item, coop);
};
return this._enumerateItems(filter);
},
setTitle: function FEED_setTitle(title) {
this._feedNode.name = title;
},
markRead: function FEED_markRead() {
var items = this._feedNode.children.enumerate();
while (items.hasMoreElements()) {
var item = items.getNext();
item.unseen = false;
}
var countsPropagator = Cc['@flock.com/stream-counts-propagator;1']
.getService(Ci.flockIStreamCountsPropagator);
countsPropagator.syncCounts(this._feedNode.resource());
},
getFolder: function FEED_getFolder() {
if (this._context) {
var parents = this._feedNode.getParents();
if (parents.length == 1) {
return new FeedFolder(parents[0], this._context, this._coop);
} else if (parents.length > 1) {
var id = this._context._contextNode.id();
for each (var parent in parents) {
var node = parent;
while (!node.isInstanceOf(this._coop.FeedContext)) {
var nodeParents = node.getParents();
if (nodeParents.length)
node = nodeParents[0];
else
break;
}
if (node.isInstanceOf(this._coop.FeedContext) && node.id() == id)
return new FeedFolder(parent, this._context, this._coop);
}
}
}
return null;
},
id: function FEED_id() {
return this._feedNode.id();
},
_enumerateItems: function FEED__enumerateItems(filterFunc) {
var feed = this;
var coop = this._coop;
var items = this._feedNode.children.enumerate();
var filteredEnumerator = {
next: null,
hasMoreElements: function() {
while (items.hasMoreElements()) {
this.next = filterFunc(feed, items.getNext(), coop);
if (this.next)
return true;
}
return false;
},
getNext: function() {
return this.next;
},
}
return filteredEnumerator;
},
getInterfaces: function FEED_getInterfaces(countRef) {
var interfaces = [Ci.flockIFeed, Ci.flockIFeedFolderItem,
Ci.flockICoopObject, Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function FEED_getHelperForLanguage(language) {
return null;
},
contractID: FEED_CONTRACTID,
classDescription: FEED_CLASSNAME,
classID: FEED_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
QueryInterface: function FEED_QueryInterface(iid) {
if (iid.equals(Ci.flockIFeed) ||
iid.equals(Ci.flockIFeedFolderItem) ||
iid.equals(Ci.flockICoopObject) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function FeedItem(feed, itemNode, coop) {
this._feed = feed;
this._itemNode = itemNode;
this._coop = coop;
}
FeedItem.prototype = {
getItemID: function FI_getItemID() {
return checkEmpty(this._itemNode.itemid);
},
getLink: function FI_getLink() {
return makeURI(this._itemNode.URL);
},
getTitle: function FI_getTitle() {
return checkEmpty(this._itemNode.name);
},
getPubDate: function FI_getPubDate() {
return this._itemNode.pubdate;
},
getCorrectedPubDate: function FI_getCorrectedPubDate() {
return this._itemNode.datevalue;
},
getAuthor: function FI_getAuthor() {
return checkEmpty(this._itemNode.author);
},
getContent: function FI_getContent() {
return gFeedStorage.getContent(this._itemNode.id());
},
getExcerpt: function FI_getExcerpt() {
return gFeedStorage.getExcerpt(this._itemNode.id());
},
getContext: function FI_getContext() {
},
isRead: function FI_isRead() {
return !this._itemNode.unseen;
},
isFlagged: function FI_isFlagged() {
return this._itemNode.flagged;
},
setRead: function FI_setRead(read) {
this._itemNode.unseen = !read;
},
setFlagged: function FI_setFlagged(flagged) {
if (this._itemNode.flagged == flagged)
return;
this._itemNode.flagged = flagged;
var feedNode = this._feed._feedNode;
var contexts = getContextsForFeed(feedNode, this._coop);
if (flagged) {
for each (var context in contexts) {
context.flaggedItems.addItem(this._itemNode);
}
} else {
for each (var context in contexts) {
context.flaggedItems.children.remove(this._itemNode);
}
}
},
getFeed: function FI_getFeed() {
return this._feed;
},
getInterfaces: function FI_getInterfaces(countRef) {
var interfaces = [Ci.flockIFeedItem, Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function FI_getHelperForLanguage(language) {
return null;
},
contractID: ITEM_CONTRACTID,
classDescription: ITEM_CLASSNAME,
classID: ITEM_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
QueryInterface: function FI_QueryInterface(iid) {
if (iid.equals(Ci.flockIFeedItem) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function FeedFolder(folderNode, context, coop) {
this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
this._logger.init('feedmanager');
this._logger.info('created folder object');
this._folderNode = folderNode;
this._context = context;
this._coop = coop;
}
FeedFolder.prototype = {
getTitle: function FF_getTitle() {
return this._folderNode.name;
},
getItemCount: function FF_getItemCount() {
},
getItems: function FF_getItems() {
},
setTitle: function FF_setTitle(title) {
this._folderNode.name = title;
},
getUnreadCount: function FF_getUnreadCount() {
return this._folderNode.unseenItems;
},
markRead: function FF_markRead() {
var children = this.getChildren();
while (children.hasMoreElements()) {
var child = children.getNext().QueryInterface(Ci.flockIFeedFolderItem);
child.markRead();
}
},
subscribeURL: function FF_subscribeURL(url, title) {
var feedObj = { URL: url, serviceId: FM_CONTRACTID };
if (title) {
title = title.replace(/[\r\n]+/g, ' ');
this._logger.info('subscribing feed ' + url + ' with title ' + title);
feedObj.name = title;
} else {
this._logger.info('subscribing feed ' + url);
}
var feedNode = new this._coop.Feed(feedObj);
if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
setFeedIndexable(feedNode, true);
this._folderNode.children.addOnce(feedNode);
if (feedNode.nextRefresh)
updateNextRefresh(feedNode, this._coop);
else
feedNode.nextRefresh = new Date();
feedNode.isPollable = true;
var feed = new Feed(feedNode, null, this._coop);
this._context.notifyOnSubscribe(feed);
return feed;
},
subscribeFeed: function FF_subscribeFeed(feed) {
var feedNode = this._subscriptionNode(feed, 'subscribing');
if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
setFeedIndexable(feedNode, true);
this._folderNode.children.addOnce(feedNode);
updateNextRefresh(feedNode, this._coop);
feedNode.isPollable = true;
this._context.notifyOnSubscribe(feed);
},
unsubscribeFeed: function FF_unsubscribeFeed(feed) {
var feedNode = this._subscriptionNode(feed, 'unsubscribing');
this._folderNode.children.remove(feedNode);
var contexts = getContextsForFeed(feedNode, this._coop);
if (contexts.length == 0)
feedNode.isPollable = false;
if (contexts.length == 0 ||
!contextHasObject(NEWS_CONTEXT_NAME, feedNode, this._coop))
setFeedIndexable(feedNode, false);
this._context.notifyOnUnsubscribe(feed);
},
subscribeFeedWithPosition:
function FF_subscribeFeedWithPosition(feed, target, orientation) {
var coop = this._coop;
var feedNode = this._subscriptionNode(feed, 'subscribing');
var check, value;
if (target instanceof Ci.flockIFeedFolder) {
value = target.getTitle();
check = function(child) {
return child.isInstanceOf(coop.FeedFolder) && child.name == value;
}
} else {
if (orientation == Ci.flockIFeedFolder.ORIENT_INSIDE)
throw Cr.NS_ERROR_INVALID_ARG;
value = target.getURL().spec;
check = function(child) {
return child.isInstanceOf(coop.Feed) && child.URL == value;
}
}
var children = this._folderNode.children.enumerate();
while (children.hasMoreElements()) {
var node = children.getNext();
if (check(node)) {
switch (orientation) {
case Ci.flockIFeedFolder.ORIENT_INSIDE:
node.children.addOnce(feedNode);
break;
case Ci.flockIFeedFolder.ORIENT_ABOVE:
var container = this._folderNode.children;
container.insertAt(feedNode, container.indexOf(node));
break;
case Ci.flockIFeedFolder.ORIENT_BELOW:
var container = this._folderNode.children;
container.insertAt(feedNode, container.indexOf(node) + 1);
break;
default:
throw Cr.NS_ERROR_INVALID_ARG;
break;
}
updateNextRefresh(feedNode, this._coop);
feedNode.isPollable = true;
if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
setFeedIndexable(feedNode, true);
this._context.notifyOnSubscribe(feed);
return;
}
}
throw Components.Exception("Couldn't find feed");
},
addFolder: function FF_addFolder(title) {
this._logger.info('adding folder ' + title);
var children = this._folderNode.children.enumerate();
while (children.hasMoreElements()) {
var child = children.getNext();
if (child.name == title) {
var msg = 'Folder already exists: ' + title;
this._logger.error(msg);
throw Components.Exception(msg);
}
}
var folder = new this._coop.FeedFolder({ name: title });
this._folderNode.children.add(folder);
return new FeedFolder(folder, this._context, this._coop);
},
removeFolder: function FF_removeFolder(folder) {
var name = folder.getTitle();
this._logger.info('removing folder ' + name);
var children = this._folderNode.children.enumerate();
while (children.hasMoreElements()) {
var child = children.getNext();
if (child.name == name && child.isInstanceOf(this._coop.FeedFolder)) {
var childFolder = new FeedFolder(child, this._context, this._coop);
childFolder._destroySelf();
return;
}
}
this._logger.warn('folder ' + name + ' not found');
},
getChildFolder: function FF_getChildFolder(title) {
var children = this._folderNode.children.enumerate();
while (children.hasMoreElements()) {
var obj = children.getNext();
if (obj.isInstanceOf(this._coop.FeedFolder) && obj.name == title)
return new FeedFolder(obj, this._context, this._coop);
}
throw Components.Exception("Folder does not exist: " + title);
},
getChildren: function FF_getChildren() {
var coop = this._coop;
var context = this._context;
var children = this._folderNode.children.enumerate();
var enumerator = {
hasMoreElements: function() {
return children.hasMoreElements();
},
getNext: function() {
var obj = children.getNext();
if (obj.isInstanceOf(coop.FeedFolder))
return new FeedFolder(obj, context, coop)
else
return new Feed(obj, context, coop)
}
};
return enumerator;
},
getFolder: function FF_getFolder() {
if (this._folderNode.isInstanceOf(this._coop.FeedContext))
return null;
var parents = this._folderNode.getParents();
if (parents.length > 0)
return new FeedFolder(parents[0], this._context, this._coop);
return null;
},
_subscriptionNode: function FF___subscriptionNode(feed, action) {
var url = feed.getURL().spec;
this._logger.info(action + ' feed ' + url + ' from folder ' +
this.getTitle());
var feedNode = getFeedNode(url, this._coop);
if (!feedNode)
throw Cr.NS_ERROR_FAILURE;
return feedNode;
},
_destroySelf: function FF__destroySelf() {
var feeds = [];
var folders = [];
var children = this._folderNode.children.enumerateBackwards();
while (children.hasMoreElements()) {
var child = children.getNext();
if (child.isInstanceOf(this._coop.FeedFolder)) {
var folder = new FeedFolder(child, this._context, this._coop);
folders.push(folder);
} else if (child.isInstanceOf(this._coop.Feed)) {
var feed = new Feed(child, this._context, this._coop);
feeds.push(feed);
}
}
for each (var folder in folders)
folder._destroySelf();
for each (var feed in feeds)
this.unsubscribeFeed(feed);
var name = this._folderNode.name;
this._folderNode.destroy();
this._logger.info('folder ' + name + ' removed');
},
getInterfaces: function FF_getInterfaces(countRef) {
var interfaces = [Ci.flockIFeedFolder, Ci.flockIFeedFolderItem,
Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function FF_getHelperForLanguage(language) {
return null;
},
contractID: FF_CONTRACTID,
classDescription: FF_CLASSNAME,
classID: FF_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
QueryInterface: function FF_QueryInterface(iid) {
if (iid.equals(Ci.flockIFeedFolder) ||
iid.equals(Ci.flockIFeedFolderItem) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function FeedContext(contextNode, coop) {
this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
this._logger.init('feedmanager');
this._logger.info('created context for ' + contextNode.name);
this._coop = coop;
this._contextNode = contextNode;
this._observers = [];
}
FeedContext.prototype = {
getName: function FC_getName() {
return this._contextNode.name;
},
getRoot: function FC_getRoot() {
return new FeedFolder(this._contextNode, this, this._coop);
},
getSubscription: function FC_getSubscription(url) {
if (!this.existsSubscription(url))
return null;
var feedNode = getFeedNode(url.spec, this._coop);
return new Feed(feedNode, this, this._coop);
},
getSubscriptions: function FC_getSubscriptions() {
var context = this;
var coop = this._coop;
var feeds = this._coop.Feed.all();
var filteredEnumerator = {
next: null,
hasMoreElements: function() {
while (feeds.hasMoreElements()) {
var feedNode = feeds.getNext();
if (contextHasObject(context._contextNode.name, feedNode, coop)) {
this.next = new Feed(feedNode, context, coop);
return true;
}
}
return false;
},
getNext: function() {
return this.next;
}
}
return filteredEnumerator;
},
existsSubscription: function FC_existsSubscription(url) {
var feedNode = getFeedNode(url.spec, this._coop);
if (!feedNode)
return false;
return contextHasObject(this._contextNode.name, feedNode, this._coop);
},
refresh: function FC_refresh() {
var feeds = this.getSubscriptions();
while (feeds.hasMoreElements()) {
var feed = feeds.getNext().QueryInterface(Ci.flockIFeed);
feed.refresh();
}
},
getRefreshInterval: function FC_getRefreshInterval() {
return this._contextNode.refreshInterval / 60;
},
setRefreshInterval: function FC_setRefreshInterval(minutes) {
this._contextNode.refreshInterval = minutes * 60;
},
getFeedItemCap: function FC_getFeedItemCap() {
},
setFeedItemCap: function FC_setFeedItemCap(maxItems) {
},
addObserver: function FC_addObserver(observer) {
function hasFunc(element, index, array) {
return element == observer;
}
if (!this._observers.some(hasFunc)) {
this._observers.push(observer);
if (this._observers.length == 1)
this._watchUnseenItems();
}
},
removeObserver: function FC_removeObserver(observer) {
function keepFunc(element, index, array) {
return element != observer;
}
this._observers = this._observers.filter(keepFunc);
if (this._observers.length == 0)
this._unwatchUnseenItems();
},
_watchUnseenItems: function FC__watchUnseenItems() {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
.getService(Ci.flockIRDFObservable);
faves.addArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
this._contextNode.resource(), unseenItems,
null, this);
},
_unwatchUnseenItems: function FC__unwatchUnseenItems() {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
.getService(Ci.flockIRDFObservable);
faves.removeArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
this._contextNode.resource(), unseenItems,
null, this);
},
rdfChanged: function FC_rdfChanged(ds, type, rsrc, pred, obj, oldObj) {
for each (var observer in this._observers) {
observer.onUnreadCountChange(this, this._contextNode.unseenItems);
}
},
notifyOnSubscribe: function FC__notifyOnSubscribe(feed) {
for each (var observer in this._observers) {
observer.onSubscribe(this, feed);
}
},
notifyOnUnsubscribe: function FC__notifyOnUnsubscribe(feed) {
for each (var observer in this._observers) {
observer.onUnsubscribe(this, feed);
}
},
getInterfaces: function FC_getInterfaces(countRef) {
var interfaces = [Ci.flockIFeedContext, Ci.flockIRDFObserver,
Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function FC_getHelperForLanguage(language) {
return null;
},
contractID: FC_CONTRACTID,
classDescription: FC_CLASSNAME,
classID: FC_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
QueryInterface: function FC_QueryInterface(iid) {
if (iid.equals(Ci.flockIFeedContext) ||
iid.equals(Ci.flockIRDFObserver) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function FeedRequest(fm, url, coop, listener, use_feed_api, metadataOnly) {
this._fm = fm;
this._url = url;
this._coop = coop;
this._listener = listener;
this._use_feed_api = use_feed_api;
this._metadataOnly = metadataOnly;
this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
this._logger.init('feedmanager');
this._channel = gIOService.newChannelFromURI(url);
try {
this._httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
}
catch (e) {
this._httpChannel = null;
}
}
FeedRequest.prototype = {
get: function FR_get() {
if (this._httpChannel) {
var feedNode = getFeedNode(this._url.spec, this._coop);
if (feedNode && feedNode.lastModification)
this._httpChannel.setRequestHeader('If-Modified-Since',
feedNode.lastModification,
false);
}
this._channel.asyncOpen(this, null);
},
onStartRequest: function FR_onStartRequest(request, context) {
var channel = request.QueryInterface(Ci.nsIChannel);
if (Components.isSuccessCode(request.status))
channel.contentType = 'text/xml';
this._processor = Cc['@mozilla.org/feed-processor;1']
.createInstance(Ci.nsIFeedProcessor);
this._processor.listener = this;
if (this._metadataOnly) {
this._processor.QueryInterface(Ci.flockIFeedProcessor);
this._processor.parseFeedMetadataAsync(null, channel.URI);
} else {
this._processor.parseAsync(null, channel.URI);
}
this._processor.onStartRequest(request, context);
},
onStopRequest: function FR_onStopRequest(request, context, status) {
if (this._processor) {
this._processor.onStopRequest(request, context, status);
this._processor = null;
}
},
onDataAvailable: function FR_onDataAvailable(request, context, inputStream,
sourceOffset, count) {
if (this._processor)
this._processor.onDataAvailable(request, context, inputStream,
sourceOffset, count);
},
handleResult: function FR_handleResult(result) {
var failed = true;
if (result && result.doc) {
var feed = this._fm.storeFeed(this._url, result);
if (feed) {
failed = false;
if (this._httpChannel && !this._metadataOnly)
this._saveLastModification(feed._feedNode);
this._notifyListener(feed);
}
} else if (this._httpChannel &&
Components.isSuccessCode(this._httpChannel.status) &&
this._httpChannel.responseStatus == 304) {
this._logger.info('Feed not modified: ' + this._url.spec);
var feedNode = getFeedNode(this._url.spec, this._coop);
if (feedNode) {
updateNextRefresh(feedNode, this._coop);
if (feedNode.state != 'failed') {
feedNode.lastFetch = new Date();
var feed = new Feed(feedNode, null, this._coop);
this._notifyListener(feed);
}
}
else if (this._listener)
this._listener.onError(null);
failed = false;
}
if (failed) {
this._logger.warn('Error processing feed: ' + this._url.spec);
if (!this._metadataOnly) {
var feedNode = getFeedNode(this._url.spec, this._coop);
if (feedNode) {
feedNode.state = 'failed';
updateNextRefresh(feedNode, this._coop);
if (this._httpChannel)
this._saveLastModification(feedNode);
}
}
if (this._listener)
this._listener.onError(null);
}
this._processor = null;
this._listener = null;
},
_notifyListener: function FR__notifyListener(feed) {
if (this._listener) {
if (this._use_feed_api)
this._listener.onGetFeedComplete(feed);
else
this._listener.onResult();
}
},
_saveLastModification: function FR__saveLastModification(feedNode) {
var lastModification = null;
try {
lastModification = this._httpChannel.getResponseHeader('Last-Modified');
}
catch (e) { }
if (lastModification)
feedNode.lastModification = lastModification;
},
QueryInterface: function FR_QueryInterface(iid) {
if (iid.equals(Ci.nsIFeedResultListener) ||
iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver)||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
}
function FeedManager() {
var obs = getObserverService();
obs.addObserver(this, 'xpcom-shutdown', false);
this._start();
}
FeedManager.prototype = {
_start: function FM__start() {
this._profiler = Cc['@flock.com/profiler;1'].getService(Ci.flockIProfiler);
var evtID = this._profiler.profileEventStart('feedmanager-init');
this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
this._logger.init('feedmanager');
this._logger.info('starting up...');
this._obsService = getObserverService();
gIOService = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
var sbs = Cc['@mozilla.org/intl/stringbundle;1']
.getService(Ci.nsIStringBundleService);
var bundle = sbs.createBundle(URI_FEED_PROPERTIES);
gReadMoreBlurb = bundle.GetStringFromName('flock.feed.temp.readmore');
this._coop = Cc["@flock.com/singleton;1"]
.getService(Ci.flockISingleton)
.getSingleton("chrome://flock/content/common/load-faves-coop.js")
.wrappedJSObject;
gFeedStorage = new FeedStorage();
var feedroot = new this._coop.Folder(URN_FEED_ROOT);
this._feedContexts = {};
this.createFeedContext(NEWS_CONTEXT_NAME);
this.createFeedContext(LIVEMARKS_CONTEXT_NAME);
this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
var prefService = Cc['@mozilla.org/preferences-service;1']
.getService(Ci.nsIPrefBranch2);
prefService.addObserver(PREF_EXPIRATION_TIME_BRANCH, this, false);
this.observe(null, 'nsPref:changed', PREF_EXPIRATION_TIME);
this.observe(null, 'nsPref:changed', PREF_METADATA_EXPIRATION_TIME);
this._profiler.profileEventEnd(evtID, '');
},
_shutdown: function FM__shutdown() {
var prefService = Cc['@mozilla.org/preferences-service;1']
.getService(Ci.nsIPrefBranch2);
prefService.removeObserver(PREF_EXPIRATION_TIME_BRANCH, this);
this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
gIOService = null;
gFeedStorage = null;
this._obsService = null;
},
_updateExpirationTimes: function FM__updateExpirationTimes(topic, pref) {
var prefService = Cc['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefService);
var prefBranch = prefService.getBranch(PREF_EXPIRATION_TIME_BRANCH);
var val = 0;
try {
val = prefBranch.getIntPref(pref);
}
catch (e) { }
if (pref == PREF_EXPIRATION_TIME) {
this._refreshExpire = val > 0 ? val : DEFAULT_EXPIRATION_TIME;
this._refreshExpire *= 60 * 1000;
} else {
this._metadataExpire = val > 0 ? val : DEFAULT_METADATA_EXPIRATION_TIME;
this._metadataExpire *= 60 * 1000;
}
},
observe: function FM_observe(subject, topic, state) {
var obs = getObserverService();
switch (topic) {
case 'xpcom-shutdown':
obs.removeObserver(this, 'xpcom-shutdown');
this._shutdown();
break;
case 'nsPref:changed':
this._updateExpirationTimes(topic, state);
break;
}
},
getFeed: function FM_getFeed(url, listener) {
var feedNode = getFeedNode(url.spec, this._coop);
if (feedNode && !feedNode.metadataOnly) {
var age = Date.now() - feedNode.lastFetch.getTime();
if (age < this._refreshExpire) {
this._logger.info("getting feed from cache: " + url.spec);
var feed = new Feed(feedNode, null, this._coop);
if (listener)
listener.onGetFeedComplete(feed);
return;
}
}
this.getFeedBypassCache(url, listener);
},
getFeedMetadata: function FM_getFeedMetadata(url, listener) {
var feedNode = getFeedNode(url.spec, this._coop);
if (feedNode) {
if (!feedNode.metadataOnly) {
this._logger.info("getting feed metadata from complete feed: " +
url.spec);
this.getFeed(url, listener);
return;
} else {
var age = Date.now() - feedNode.lastFetch.getTime();
if (age < this._metadataExpire) {
this._logger.info("getting feed metadata from cache: " + url.spec);
var feed = new Feed(feedNode, null, this._coop);
if (listener)
listener.onGetFeedComplete(feed);
return;
}
}
}
this._logger.info("getting feed metadata from network: " + url.spec);
var req = new FeedRequest(this, url, this._coop, listener, true, true);
req.get();
},
getFeedBypassCache: function FM_getFeedBypassCache(url, listener) {
this._logger.info("getting feed from network: " + url.spec);
var req = new FeedRequest(this, url, this._coop, listener, true, false);
req.get();
},
createFeedContext: function FM_createFeedContext(name) {
var ctxt = this._feedContexts[name];
if (ctxt)
return ctxt;
var context = new this._coop.FeedContext({ name: name });
context.flaggedItems = new this._coop.FeedFlaggedStream({ context: context });
var feedroot = this._coop.get(URN_FEED_ROOT);
feedroot.children.addOnce(context);
ctxt = new FeedContext(context, this._coop);
this._feedContexts[name] = ctxt;
return ctxt;
},
getFeedContext: function FM_getFeedContext(name) {
var ctxt = this._feedContexts[name];
if (ctxt)
return ctxt;
var urn = this._coop.FeedContext.get_id({ name: name });
var context = this._coop.get(urn);
if (context) {
ctxt = new FeedContext(context, this._coop);
this._feedContexts[name] = ctxt;
return ctxt;
} else {
throw Components.Exception("No feed context named " + name);
}
},
getFeedContexts: function FM_getFeedContexts() {
var contexts = this._coop.FeedContext.all();
var fm = this;
var ctor = function(contextNode, coop) {
var ctxt = fm._feedContexts[contextNode.name];
if (!ctxt) {
ctxt = new FeedContext(contextNode, coop);
fm.feedContexts[contextNode.name] = ctxt;
}
return ctxt;
}
return this._enumerateObjects(contexts, ctor);
},
deleteFeedContext: function FM_deleteFeedContext(name) {
var urn = this._coop.FeedContext.get_id({ name: name });
var context = this._coop.get(urn);
if (!context)
throw Components.Exception("No feed context named " + name);
delete this._feedContexts[name];
context.flaggedItems.destroy();
// FIXME: delete folders as well
context.destroy();
},
existsFeed: function FM_existsFeed(url) {
var urn = this._coop.Feed.get_id({ URL: url.spec });
return this._coop.Feed.exists(urn);
},
refresh: function FM_refresh(urn, listener) {
this._logger.info('refreshing feed: ' + urn);
var feedNode = this._coop.get(urn);
if (feedNode) {
/*
if (!feedNode.metadataOnly) {
var age = Date.now() - feedNode.lastFetch.getTime();
if (age < this._refreshExpire) {
if (listener)
listener.onResult();
return;
}
}
*/
var req = new FeedRequest(this, makeURI(feedNode.URL), this._coop,
listener, false, false);
req.get();
} else {
var msg = 'could not get feed: ' + urn;
this._logger.error(msg);
throw Components.Exception(msg, Cr.NS_ERROR_UNEXPECTED);
}
},
getFeedFolderItem: function FM_getFeedFolderItem(urn) {
this._logger.info('getting feed folder item: ' + urn);
var node = this._coop.get(urn);
if (node) {
var context = this.getFeedContext(NEWS_CONTEXT_NAME);
if (node.isInstanceOf(this._coop.Feed)) {
return new Feed(node, context, this._coop);
} else if (node.isInstanceOf(this._coop.FeedFolder)) {
return new FeedFolder(node, context, this._coop);
}
}
this._logger.warn('could not get feed folder item: ' + urn);
return null;
},
getFeedItem: function FM_getFeedItem(urn) {
this._logger.info('getting feed item: ' + urn);
var node = this._coop.get(urn);
if (node && node.isInstanceOf(this._coop.FeedItem)) {
var parents = node.getParents();
for each (var parent in parents) {
if (parent.isInstanceOf(this._coop.Feed)) {
var context = this.getFeedContext(NEWS_CONTEXT_NAME);
var feed = new Feed(parent, context, this._coop);
return new FeedItem(feed, node, this._coop);
}
}
}
this._logger.warn('could not get feed item: ' + urn);
return null;
},
getLibrary: function FM_getLibrary() {
var feeds = this._coop.Feed.all();
var ctor = function(feedNode, coop) {
return new Feed(feedNode, null, coop);
}
return this._enumerateObjects(feed, ctor);
},
storeFeed: function FM_storeFeed(feedURL, feedResult) {
if (feedResult.bozo)
return null;
var feed = feedResult.doc;
feed.QueryInterface(Ci.nsIFeed);
var flockFeed = feed.QueryInterface(Ci.flockIFeedContainer);
if (flockFeed.metadataOnly) {
var evtID = this._profiler.profileEventStart('feedmanager-store-feed-metadata');
this._logger.debug('storing feed metadata: ' + feedURL.spec);
} else {
var evtID = this._profiler.profileEventStart('feedmanager-store-feed');
this._logger.info('storing feed: ' + feedURL.spec);
}
var title = feed.title ? feed.title.plainText() : null;
var subtitle = feed.subtitle ? feed.subtitle.plainText() : null;
var format = feedResult.version ? feedResult.version : UNKNOWN_FEED_TYPE;
var author = null;
if (feed.authors && feed.authors.length)
author = feed.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
var image = null;
try {
image = makeURI(feed.image.getPropertyAsAString('url'));
}
catch (e) { }
var existingFeed = getFeedNode(feedURL.spec, this._coop);
if (existingFeed)
title = existingFeed.name;
var feedNode = this._newFeedNode(feedURL, feedResult.uri,
title, subtitle, feed.link, author,
format, image, flockFeed.metadataOnly);
feedNode.lastFetch = new Date();
if (flockFeed.metadataOnly) {
this._profiler.profileEventEnd(evtID, feedURL.spec);
return new Feed(feedNode, null, this._coop);
}
updateNextRefresh(feedNode, this._coop);
insertFavicon(feedNode);
var feedItems = feed.items;
var numItems = feedItems.length;
var items = [];
for (var i = 0; i < numItems; i++) {
var item = feedItems.queryElementAt(i, Ci.nsIFeedEntry);
var datevalue = item.updated ? new Date(item.updated)
: feedNode.datevalue;
items.push({item: item, datevalue: datevalue, indexvalue: i});
}
items.sort(feedItemSorter);
var refDate;
var children = feedNode.children.enumerateBackwards();
if (children.hasMoreElements())
refDate = children.getNext().datevalue;
else
refDate = new Date(0);
gFeedStorage.beginTransaction();
try {
var itemsAdded = 0;
for each (var i in items) {
var node = this._storeFeedEntry(i.item, feedNode, refDate, feed);
if (node)
itemsAdded++;
}
}
finally {
gFeedStorage.commitTransaction();
}
children = feedNode.children.enumerateBackwards();
if (children.hasMoreElements())
feedNode.datevalue = children.getNext().datevalue;
else
feedNode.datevalue = new Date();
var feedObj = new Feed(feedNode, null, this._coop);
if (itemsAdded && existingFeed)
this._obsService.notifyObservers(feedObj, 'new-feed-items', null);
this._profiler.profileEventEnd(evtID, feedURL.spec);
return feedObj;
},
_newFeedNode: function FM__newFeedNode(feedURL, finalURL, title, subtitle,
link, author, format, image,
metadataOnly) {
var feedInfo = { URL: feedURL.spec,
finalURL: finalURL ? finalURL.spec : feedURL.spec,
name: title ? title : feedURL.spec,
subtitle: subtitle,
link: link ? link.spec : feedURL.spec,
author: author ? author : null,
format: format ? format : UNKNOWN_FEED_TYPE,
image: image ? image.spec : null,
metadataOnly: metadataOnly,
state: 'ok',
serviceId: FM_CONTRACTID
};
return new this._coop.Feed(feedInfo);
},
_storeFeedEntry: function FM__storeFeedEntry(item, feedNode, refDate, feed) {
var title = null;
if (item.title)
title = item.title.plainText();
var author = null;
if (item.authors && item.authors.length)
author = item.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
var pubdate = item.published;
if (!pubdate)
pubdate = item.updated;
if (!pubdate)
pubdate = feed.updated;
var content = null;
if (item.content)
content = item.content;
else if (item.summary)
content = item.summary;
var excerptSource = null;
if (item.summary)
excerptSource = item.summary.plainText();
else if (item.content)
excerptSource = item.content.plainText();
return this._storeFeedItem(feedNode, item.id, title,
item.link, author, pubdate, refDate,
content, excerptSource,
false, false);
},
_storeFeedItem: function FM__storeFeedItem(feedNode, id, title, link, author,
pubdate, refDate,
content, excerptSource,
read, flagged) {
if (!id && link)
id = link.spec;
if (!link)
link = makeURI(feedNode.URL);
if (!title && excerptSource)
title = excerptSource.substr(0, TITLE_TRIM_MAX_CHARS) + '...';
if (title)
title = title.replace(/[\r\n]+/g, ' ');
else
title = link.spec;
if (pubdate)
pubdate = new Date(pubdate);
var itemInfo = { itemid: id,
URL: link.spec,
name: title,
author: author,
unseen: !read,
flagged: flagged,
parentfeed: feedNode.URL
};
//TODO: we'll want to be more intelligent about this, but the goal should be to only update feed items that need updating
var rdfId = this._coop.FeedItem.get_id(itemInfo);
if (rdfId && this._coop.FeedItem.exists(rdfId))
return null;
itemInfo.pubdate = pubdate;
if (!pubdate)
itemInfo.datevalue = new Date();
else if (pubdate.getTime() > Date.now())
itemInfo.datevalue = new Date();
else if (pubdate < refDate)
itemInfo.datevalue = new Date();
else
itemInfo.datevalue = pubdate;
itemInfo.isIndexable = feedNode.isIndexable;
this._logger.info('storing feeditem: ' + itemInfo.name);
var itemNode = new this._coop.FeedItem(itemInfo);
if (feedNode.children.indexOf(itemNode) < 0)
feedNode.addItem(itemNode);
if (content) {
if (typeof(content) != 'string') {
var parser = Cc['@mozilla.org/xmlextras/domparser;1']
.createInstance(Ci.nsIDOMParser);
var doc = parser.parseFromString('<div/>', 'application/xhtml+xml');
var docElem = doc.documentElement;
var fragment = content.createDocumentFragment(docElem);
docElem.appendChild(fragment);
this._filterFeedItemContent(doc, content.base);
var serializer = Cc['@mozilla.org/xmlextras/xmlserializer;1']
.createInstance(Ci.nsIDOMSerializer);
var data = serializer.serializeToString(doc);
} else {
data = content;
}
var excerpt = excerptText(excerptSource, itemInfo.URL);
gFeedStorage.saveItem(itemNode.id(), feedNode.URL, data, excerpt);
} else {
gFeedStorage.deleteItem(itemNode.id());
}
return itemNode;
},
_filterFeedItemContent: function FM__filterFeedItemContent(doc, baseURI) {
if (!baseURI)
return;
var tw = doc.createTreeWalker(doc.documentElement,
Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
null, false);
node = tw.nextNode();
while (node) {
for each (var attrName in ATTR_URI_LIST) {
var attr = node.attributes.getNamedItem(attrName);
if (attr) {
var nval = attr.nodeValue;
try {
var newURI = gIOService.newURI(nval, null, baseURI);
attr.nodeValue = newURI.spec;
}
catch (e) { }
}
}
node = tw.nextNode();
}
},
_deleteItemContent: function FM__deleteItemContent(item, coop) {
gFeedStorage.deleteItem(item.id());
},
_purgeFeed: function FM__purgeFeed(feed) {
this._logger.info('Purging ' + feed.id());
var items = feed.children.enumerateBackwards();
if (items.hasMoreElements()) {
this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
gFeedStorage.deleteFeed(feed.URL);
while (items.hasMoreElements())
items.getNext().destroy();
this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
}
feed.destroy();
},
_enumerateObjects: function FM__enumerateObjects(objects, ctor) {
var coop = this._coop;
var enumerator = {
hasMoreElements: function() {
return objects.hasMoreElements();
},
getNext: function() {
return ctor(objects.getNext(), coop);
}
}
return enumerator;
},
_purgeOrphanedFeeds: function FM__purgeOrphanedFeeds() {
var feeds = this._coop.Feed.all();
var fm = this;
var purge = {
notify: function(timer) {
var start = Date.now();
while (feeds.hasMoreElements()) {
var feed = feeds.getNext();
var contexts = getContextsForFeed(feed, fm._coop);
if (contexts.length == 0)
fm._purgeFeed(feed);
if (Date.now() > start + PURGE_RUN_INTERVAL)
return;
}
timer.cancel();
}
};
var timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
timer.initWithCallback(purge, PURGE_SLEEP_INTERVAL,
Ci.nsITimer.TYPE_REPEATING_SLACK);
},
/* flockIHousekeeping interface */
runHousekeeping: function FM_runHousekeeping() {
this._logger.info('running housekeeping...');
this._purgeOrphanedFeeds();
},
/* flockIMigratable interface */
get migrationName() { return "News Feeds"; },
needsMigration: function FM_needsMigration(oldVersion) {
var oldFeedsFile = this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE);
return oldFeedsFile.exists();
},
startMigration: function FM_startMigration(oldVersion, listener) {
var obs = getObserverService();
obs.notifyObservers(null, 'myworld-enableDisable-observers', false);
this._start();
gFeedStorage.beginTransaction();
var ctxt = {
listener: listener,
oldFeedsFile : this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE),
oldFlaggedFile : this._getOldFeedServiceFile(OLD_FLAGGED_RDF_FILE),
oldRootFile : this._getOldFeedServiceFile(OLD_ROOT_RDF_FILE),
oldDiscoveryFile : this._getOldFeedServiceFile(OLD_DISCOVERY_RDF_FILE),
oldFeedDir : this._getOldFeedServiceFile(OLD_FEED_DATA_DIR),
oldFeedsReported : false,
oldFlaggedReported : false,
cleanupReported : false,
};
return { wrappedJSObject: ctxt };
},
finishMigration: function FM_finishMigration(ctxtWrapper) {
gFeedStorage.commitTransaction();
var obs = getObserverService();
obs.notifyObservers(null, 'myworld-enableDisable-observers', true);
},
doMigrationWork: function FM_doMigrationWork(ctxtWrapper) {
var ctxt = ctxtWrapper.wrappedJSObject;
if (!ctxt.oldFeedsReported) {
ctxt.listener.onUpdate(0, 'Migrating subscriptions');
ctxt.oldFeedsReported = true;
} else if (ctxt.oldFeedsFile.exists()) {
if (!ctxt.oldFeedsMigrator)
ctxt.oldFeedsMigrator = this._migrateOldFeeds(ctxt);
if (ctxt.oldFeedsMigrator.next())
ctxt.oldFeedsMigrator = null;
} else if (!ctxt.oldFlaggedReported) {
ctxt.listener.onUpdate(-1, 'Migrating saved articles');
ctxt.oldFlaggedReported = true;
} else if (ctxt.oldFlaggedFile.exists()) {
this._migrateOldFlagged(ctxt);
} else if (!ctxt.cleanupReported) {
ctxt.listener.onUpdate(-1, 'Cleaning up old data');
ctxt.cleanupReported = true;
} else if (ctxt.oldRootFile.exists()) {
ctxt.oldRootFile.remove(false);
} else if (ctxt.oldDiscoveryFile.exists()) {
ctxt.oldDiscoveryFile.remove(false);
} else if (ctxt.oldFeedDir.exists()) {
ctxt.oldFeedDir.remove(true);
return false;
} else {
return false;
}
return true;
},
_getOldFeedServiceFile: function FM__getOldFeedServiceFile(filename) {
var oldFile = Cc['@mozilla.org/file/directory_service;1']
.getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
oldFile.append(filename)
return oldFile;
},
_migrateOldFeeds: function FM__migrateOldFeeds(ctxt) {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
.getService(Ci.nsIRDFContainerUtils);
var spec = gIOService.newFileURI(ctxt.oldFeedsFile).spec;
var ds = RDFS.GetDataSourceBlocking(spec);
var root = RDFS.GetResource(SUBSCRIPTIONS_RDF_ROOT);
var context = this.getFeedContext(NEWS_CONTEXT_NAME);
var news_root = context.getRoot();
var titleProp = RDFS.GetResource(FLOCK_NS + 'title');
var typeProp = RDFS.GetResource(FLOCK_NS + 'type');
var feeds = ds.GetSources(typeProp, RDFS.GetLiteral('feed'), true);
var numFeeds = 0;
while (feeds && feeds.hasMoreElements()) {
var feed = feeds.getNext();
numFeeds++;
}
var children = RDFCU.MakeSeq(ds, root).GetElements(), i = 0;
while (children && children.hasMoreElements()) {
var child = children.getNext();
child.QueryInterface(Ci.nsIRDFResource);
if (RDFCU.IsContainer(ds, child)) {
var title = ds.GetTarget(child, titleProp, true);
if (!title)
continue;
title.QueryInterface(Ci.nsIRDFLiteral);
if (!title.Value)
continue;
var subFolder = news_root.addFolder(title.Value);
var subChildren = RDFCU.MakeSeq(ds, child).GetElements();
while (subChildren && subChildren.hasMoreElements()) {
var subChild = subChildren.getNext();
subChild.QueryInterface(Ci.nsIRDFResource);
var url = subChild.Value;
var percent = Math.round(i / numFeeds * 100);
ctxt.listener.onUpdate(percent, 'Migrating ' + url);
yield false;
i++;
this._migrateFeed(url, subFolder);
}
} else {
var url = child.Value;
var percent = Math.round(i / numFeeds * 100);
ctxt.listener.onUpdate(percent, 'Migrating ' + url);
yield false;
i++;
this._migrateFeed(url, news_root);
}
}
var oldFile = ctxt.oldFeedsFile.clone();
oldFile.moveTo(null, OLD_FEEDS_RDF_FILE_RELIC);
//ctxt.oldFeedsFile.remove(false);
yield true;
},
_migrateFeed: function FM__migrateFeed(url, folder) {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
.getService(Ci.nsIRDFContainerUtils);
var oldFeedFile = this._getOldFeedServiceFile(OLD_FEED_DATA_DIR);
oldFeedFile.append(FlockCryptoHash.md5(url));
oldFeedFile.append(OLD_FEED_DATA_FILENAME);
if (!oldFeedFile.exists())
return;
var spec = gIOService.newFileURI(oldFeedFile).spec;
var ds = RDFS.GetDataSourceBlocking(spec);
var root = RDFS.GetResource(OLD_FEED_DATA_RDF_PREFIX + url);
function getTarget(prop) {
var t = ds.GetTarget(root, prop, true);
return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
}
var title = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
var subtitle = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
var author = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
var link = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
var format = getTarget(RDFS.GetResource(FLOCK_NS + 'feedtype'));
var image = getTarget(RDFS.GetResource(FLOCK_NS + 'image'));
var favicon = getTarget(RDFS.GetResource(FLOCK_NS + 'favicon'));
if (FORMAT_CONVERSIONS[format])
format = FORMAT_CONVERSIONS[format];
else if (format.indexOf('RSS') == 0)
format = 'rssUnknown';
else
format = null;
var feedURL = makeURI(url);
if (!feedURL)
return;
var feedNode = this._newFeedNode(feedURL, null, title, subtitle,
makeURI(link), author, format,
makeURI(image), false);
var faviconURL = makeURI(favicon);
if (faviconURL)
feedNode.favicon = faviconURL.spec;
folder.subscribeFeed(new Feed(feedNode, null, this._coop));
var items = [];
var children = RDFCU.MakeSeq(ds, root).GetElements();
while (children && children.hasMoreElements()) {
var item = this._migrateFeedItem(ds, children.getNext(), false);
if (item)
items.push(item);
}
items.sort(feedItemSorter);
for each (var i in items) {
var args = i.item;
args.unshift(feedNode);
this._storeFeedItem.apply(this, args);
}
children = feedNode.children.enumerateBackwards();
if (children.hasMoreElements())
feedNode.datevalue = children.getNext().datevalue;
else
feedNode.datevalue = new Date();
},
_migrateOldFlagged: function FM__migrateOldFlagged(ctxt) {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
.getService(Ci.nsIRDFContainerUtils);
var spec = gIOService.newFileURI(ctxt.oldFlaggedFile).spec;
var ds = RDFS.GetDataSourceBlocking(spec);
var root = RDFS.GetResource(FLAGGED_RDF_ROOT);
var context = new this._coop.FeedContext({ name: NEWS_CONTEXT_NAME });
var flaggedItems = context.flaggedItems;
var feedProp = RDFS.GetResource(FLOCK_NS + 'feed');
var children = RDFCU.MakeSeq(ds, root).GetElements();
while (children && children.hasMoreElements()) {
var child = children.getNext();
child.QueryInterface(Ci.nsIRDFResource);
var feedURL = ds.GetTarget(child, feedProp, true);
if (!feedURL)
continue;
feedURL.QueryInterface(Ci.nsIRDFLiteral);
var feedNode = getFeedNode(feedURL.Value, this._coop);
if (feedNode) {
var args = this._migrateFeedItem(ds, child, true).item;
args.unshift(feedNode);
var node = this._storeFeedItem.apply(this, args);
if (node)
flaggedItems.addItem(node);
}
}
var oldFile = ctxt.oldFlaggedFile.clone();
oldFile.moveTo(null, OLD_FLAGGED_RDF_FILE_RELIC);
//ctxt.oldFlaggedFile.remove(false);
},
_migrateFeedItem: function FM__migrateFeedItem(ds, res, doFlagged) {
var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
.getService(Ci.nsIRDFService);
res.QueryInterface(Ci.nsIRDFResource);
function getTarget(prop) {
var t = ds.GetTarget(res, prop, true);
return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
}
var flagged = getTarget(RDFS.GetResource(FLOCK_NS + 'flagged'));
if (flagged == 'true' && !doFlagged)
return;
var id = res.Value;
if (id.indexOf(OLD_FEED_DATA_POST_PREFIX) == 0)
id = id.substr(OLD_FEED_DATA_POST_PREFIX.length);
else
id = null;
var title = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
var link = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
var datevalue = getTarget(RDFS.GetResource(FLOCK_NS + 'datevalue'));
var author = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
var content = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
var read = getTarget(RDFS.GetResource(FLOCK_NS + 'read'));
datevalue = new Date(Number(datevalue));
refDate = new Date(0);
var item = [id, title, makeURI(link), author, datevalue, refDate, content,
content, read == 'true', doFlagged];
return ({ item: item, datevalue: datevalue, indexvalue: 0 });
},
getInterfaces: function FM_getInterfaces(countRef) {
var interfaces = [Ci.flockIFeedManager, Ci.flockIPollingService,
Ci.flockIMigratable, Ci.flockIHousekeeping,
Ci.nsIObserver, Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function FM_getHelperForLanguage(language) {
return null;
},
contractID: FM_CONTRACTID,
classDescription: FM_CLASSNAME,
classID: FM_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
flags: Ci.nsIClassInfo.SINGLETON,
QueryInterface: function FM_QueryInterface(iid) {
if (iid.equals(Ci.flockIFeedManager) ||
iid.equals(Ci.flockIPollingService) ||
iid.equals(Ci.flockIMigratable) ||
iid.equals(Ci.flockIHousekeeping) ||
iid.equals(Ci.nsIObserver) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
function GenericComponentFactory(ctor) {
this._ctor = ctor;
}
GenericComponentFactory.prototype = {
_ctor: null,
// nsIFactory
createInstance: function(outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return (new this._ctor()).QueryInterface(iid);
},
// nsISupports
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIFactory) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
};
var Module = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIModule) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
getClassObject: function(cm, cid, iid) {
if (!iid.equals(Ci.nsIFactory))
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
if (cid.equals(FEED_CLASSID))
return new GenericComponentFactory(Feed);
if (cid.equals(ITEM_CLASSID))
return new GenericComponentFactory(FeedItem);
if (cid.equals(FC_CLASSID))
return new GenericComponentFactory(FeedContext);
if (cid.equals(FF_CLASSID))
return new GenericComponentFactory(FeedFolder);
if (cid.equals(FM_CLASSID))
return new GenericComponentFactory(FeedManager);
throw Cr.NS_ERROR_NO_INTERFACE;
},
registerSelf: function(cm, file, location, type) {
var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
cr.registerFactoryLocation(FEED_CLASSID, FEED_CLASSNAME, FEED_CONTRACTID,
file, location, type);
cr.registerFactoryLocation(ITEM_CLASSID, ITEM_CLASSNAME, ITEM_CONTRACTID,
file, location, type);
cr.registerFactoryLocation(FC_CLASSID, FC_CLASSNAME, FC_CONTRACTID,
file, location, type);
cr.registerFactoryLocation(FF_CLASSID, FF_CLASSNAME, FF_CONTRACTID,
file, location, type);
cr.registerFactoryLocation(FM_CLASSID, FM_CLASSNAME, FM_CONTRACTID,
file, location, type);
var catman = Cc['@mozilla.org/categorymanager;1']
.getService(Ci.nsICategoryManager);
catman.addCategoryEntry('flock-rdf-setup', FM_CLASSNAME,
'service,' + FM_CONTRACTID,
true, true);
catman.addCategoryEntry('flockMigratable', FM_CLASSNAME, FM_CONTRACTID,
true, true);
catman.addCategoryEntry('flockHousekeeping', FM_CLASSNAME, FM_CONTRACTID,
true, true);
},
unregisterSelf: function(cm, location, type) {
var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
cr.unregisterFactoryLocation(FEED_CLASSID, location);
cr.unregisterFactoryLocation(ITEM_CLASSID, location);
cr.unregisterFactoryLocation(FC_CLASSID, location);
cr.unregisterFactoryLocation(FF_CLASSID, location);
cr.unregisterFactoryLocation(FM_CLASSID, location);
},
canUnload: function(cm) {
return true;
},
};
function NSGetModule(compMgr, fileSpec)
{
return Module;
}